Imports System.Net
Imports System.Text.RegularExpressions
Imports System.Threading
Imports System.Web.HttpUtility

Module Login

    Public CaptchaId, CaptchaWord As String, Proxy As IWebProxy

    Public Sub ConfigureProxy(ByVal Enabled As Boolean, ByVal Address As String, ByVal Port As Integer, _
        ByVal Username As String, ByVal Password As String, ByVal Domain As String)

        Dim Wp As WebProxy

        If (Address = "") Then
            Port = 80

            Dim ProxyString As String = CStr(My.Computer.Registry.GetValue _
                ("HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Internet Settings", "ProxyServer", ""))

            Dim ProxyEnabled As Boolean = CBool(My.Computer.Registry.GetValue _
                ("HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Internet Settings", "ProxyEnable", False))

            If (Not ProxyEnabled Or ProxyString = "") Then
                Wp = New WebProxy
            Else
                Wp = New WebProxy("http://" & ProxyString & "/", True)
            End If
        ElseIf Enabled Then
            If Port <= 0 Or Port >= 65536 Then Port = 80

            Wp = New WebProxy("http://" & Address & ":" & CStr(Port) & "/", True)
        Else
            Wp = New WebProxy
        End If

        Wp.Credentials = New NetworkCredential(Username, Password, Domain)
        Wp.UseDefaultCredentials = True
        Proxy = Wp
    End Sub

End Module

Namespace Requests

    Class LoginRequest : Inherits Request

        Public LoginForm As LoginForm

        Public Sub Start()
            Dim RequestThread As New Thread(AddressOf Process)
            RequestThread.IsBackground = True
            RequestThread.Start()
        End Sub

        Private Sub Process()
            'Log in... can't use the API here because it locks you out after a wrong password
            UpdateStatus(Msg("login-progress-start"))

            Try
                Dim LoginResult As LoginResult = DoLogin()

                Select Case LoginResult
                    'Outcomes for what posibly could go wrong when logging in
                    Case LoginResult.Failed
                        Abort(Msg("login-error-unknown"))

                    Case LoginResult.NoUser
                        Abort(Msg("login-error-nouser"))

                    Case LoginResult.InvalidUsername
                        Abort(Msg("login-error-invalid"))

                    Case LoginResult.CaptchaNeeded
                        Callback(AddressOf CaptchaNeeded)

                    Case LoginResult.WrongPassword
                        If CaptchaId Is Nothing Then Abort(Msg("login-error-password")) _
                            Else Abort(Msg("login-error-captcha"))
                        CaptchaId = Nothing
                End Select

                If LoginResult <> LoginResult.Success Then Exit Sub

            Catch ex As WebException
                'Trap errors resulting from failed login
                Abort(ex.Message)
                Exit Sub
            End Try

            If State = States.Cancelled Then Thread.CurrentThread.Abort()

            'Get global configuration
            UpdateStatus(Msg("login-progress-global"))

            Dim GlobalConfigRequest As New GlobalConfigRequest

            If Not GlobalConfigRequest.Process Then
                Abort(Msg("login-error-global"))
                Exit Sub
            End If

            If Not Config.EnabledForAll Then
                Abort(Msg("login-error-alldisabled"))
                Exit Sub
            End If

            If State = States.Cancelled Then Thread.CurrentThread.Abort()

            'Connect to IRC, if required (on separate thread)
            If Config.IrcMode AndAlso (Config.IrcChannel IsNot Nothing) Then IrcConnect()

            'Get project configuration
            UpdateStatus(Msg("login-progress-project"))

            Dim ConfigRequest As New ConfigRequest

            If Not ConfigRequest.GetProjectConfig Then
                Abort(Msg("login-error-project"))
                Exit Sub
            End If

            'Notify user of new version
            If Config.LatestVersion > Config.Version Then
                If Config.MinVersion > Config.Version Then
                    Abort(Msg("login-error-version"))
                    Callback(AddressOf ShowNewVersionForm)
                    Exit Sub
                End If

                Callback(AddressOf ShowNewVersionForm)
            End If

            If Not Config.EnabledForAll Then
                Abort(Msg("login-error-projdisabled"))
                Exit Sub
            End If

            If State = States.Cancelled Then Thread.CurrentThread.Abort()

            'Get user configuration
            UpdateStatus(Msg("login-progress-user"))

            Dim UserConfigResult As Boolean = ConfigRequest.GetUserConfig

            If Config.RequireConfig AndAlso (Not UserConfigResult OrElse Not Config.Enabled) Then
                Config.ConfigChanged = True
                Abort(Msg("login-error-disabled"))
                Exit Sub
            End If

            If Config.TemplateMessages.Count = 0 Then Config.TemplateMessages = Config.TemplateMessagesGlobal

            If Config.WarnSummary2 Is Nothing Then Config.WarnSummary2 = Config.WarnSummary
            If Config.WarnSummary3 Is Nothing Then Config.WarnSummary3 = Config.WarnSummary
            If Config.WarnSummary4 Is Nothing Then Config.WarnSummary4 = Config.WarnSummary

            If State = States.Cancelled Then Thread.CurrentThread.Abort()

            'Get user information and groups
            UpdateStatus(Msg("login-progress-rights"))

            Dim Result As String = GetApi("action=query&format=xml&meta=userinfo&uiprop=rights|editcount")

            If Result.Contains("<userinfo ") AndAlso Result.Contains("<rights>") Then
                Result = Result.Substring(Result.IndexOf("<userinfo "))
                Result = Result.Substring(0, Result.IndexOf("</userinfo>"))

                If Result.Contains("anon=""""") Then
                    Abort(Msg("login-error-rights"))
                    Exit Sub
                End If

                Dim EditCount As Integer = CInt(FindString(Result, "editcount=""", """"))

                If EditCount < Config.RequireEdits Then
                    Abort(Msg("login-error-count", CStr(Config.RequireEdits)))
                    Exit Sub
                End If

                Result = FindString(Result, "<rights>", "</rights>")

                Dim Rights As New List(Of String)(Result.Split(New String() {"<r>"}, _
                    StringSplitOptions.RemoveEmptyEntries))
                Dim Autoconfirmed, AdminAvailable As Boolean

                For Each Item As String In Rights
                    Item = Item.Replace("</r>", "").Trim(" "c, LF).ToLower
                    If Item = "rollback" Then RollbackAvailable = True
                    If Item = "autoconfirmed" Then Autoconfirmed = True
                    If Item = "block" Then AdminAvailable = True
                Next Item

                Administrator = AdminAvailable AndAlso Config.UseAdminFunctions

                If Config.RequireAdmin AndAlso Not AdminAvailable Then
                    Abort(Msg("login-error-admin"))
                    Exit Sub
                End If

                If Not Autoconfirmed AndAlso Config.Project = "en.wikipedia" Then
                    Abort(Msg("login-error-autoconfirmed"))
                    Exit Sub
                End If

                If Config.RequireRollback AndAlso Not RollbackAvailable Then
                    Abort(Msg("login-error-rollback"))
                    Exit Sub
                End If

            Else
                Abort(Msg("login-error-rights"))
                Exit Sub
            End If

            If State = States.Cancelled Then Thread.CurrentThread.Abort()

            If Config.RequireTime > 0 Then
                'Get account creation date
                UpdateStatus(Msg("login-progress-age"))

                Result = GetApi("action=query&format=xml&list=logevents&letype=newusers&letitle=" & _
                    UrlEncode(User.Me.UserPage.Name))

                'We know the user exists, so if we get an empty result the user must have been created in 2005 or
                'earlier, before the log existed
                If Not Result.Contains("<logevents />") AndAlso Result.Contains("<logevents>") Then
                    Dim CreationDateString As String = Result.Substring(Result.IndexOf("<logevents>"))
                    CreationDateString = Result.Substring(Result.IndexOf("timestamp=""") + 11)
                    CreationDateString = Result.Substring(0, Result.IndexOf(""""))

                    Dim CreationDate As Date
                    Date.TryParse(CreationDateString, CreationDate)

                    If CreationDate.AddDays(Config.RequireTime) < Date.UtcNow Then
                        Abort(Msg("login-error-age", CStr(Config.RequireTime)))
                        Exit Sub
                    End If
                End If
            End If

            If State = States.Cancelled Then Thread.CurrentThread.Abort()

            'Check user list. If approval required, deny access to users not on the list, otherwise add them
            If Config.UserListLocation IsNot Nothing Then
                UpdateStatus(Msg("login-progress-userlist"))

                Dim UserList As String = GetPageText(Config.UserListLocation)

                If UserList Is Nothing AndAlso Config.Approval Then
                    Abort(Msg("login-error-userlist"))
                    Exit Sub
                End If

                If UserList IsNot Nothing AndAlso Not _
                    UserList.Contains("[[Special:Contributions/" & Config.Username & "|" & Config.Username & "]]") Then

                    If Config.Approval Then
                        Abort(Msg("login-error-approval"))
                        Exit Sub
                    End If

                    Dim Matches As MatchCollection = _
                        New Regex("\* \[\[Special:Contributions/([^\|]+)\|[^\|]+\]\]").Matches(UserList)
                    Dim ListedUsers As New List(Of String)

                    For Each Item As Match In Matches
                        If Not ListedUsers.Contains(Item.Groups(1).Value) Then ListedUsers.Add(Item.Groups(1).Value)
                    Next Item

                    ListedUsers.Add(Config.Username)
                    ListedUsers.Sort(AddressOf CompareUsernames)

                    Dim Data As EditData = GetEditData(Config.UserListLocation)
                    Data.Text = "{{/Header}}" & LF

                    For Each Item As String In ListedUsers
                        Data.Text &= "* [[Special:Contributions/" & Item & "|" & Item & "]]" & LF
                    Next Item

                    Data.Minor = True
                    Data.Summary = Config.UserListUpdateSummary.Replace("$1", Config.Username)
                    PostEdit(Data)
                End If
            End If

            If State = States.Cancelled Then Thread.CurrentThread.Abort()

            If Config.WhitelistLocation IsNot Nothing Then
                'Get whitelist
                UpdateStatus(Msg("login-progress-whitelist"))

                Result = GetApi("action=query&format=xml&titles=" & Config.WhitelistLocation & _
                    "&prop=revisions&rvlimit=1&rvprop=content")

                If Result Is Nothing Then
                    Abort(Msg("login-error-whitelist"))
                    Exit Sub
                End If

                If Result.Contains("<rev>") AndAlso Result.Contains("</rev>") Then
                    Result = Result.Substring(Result.IndexOf("<rev>") + 5)
                    Result = Result.Substring(0, Result.IndexOf("</rev>"))
                    Result = HtmlDecode(Result)

                    Whitelist.AddRange(Result.Split(New String() {LF}, StringSplitOptions.None))
                End If

                WhitelistLoaded = True
            End If

            'In case user is not already on the whitelist (usually will be)
            User.Me.Ignored = True

            If State = States.Cancelled Then Thread.CurrentThread.Abort()

            'Get bot list
            UpdateStatus(Msg("login-progress-botlist"))

            Result = GetApi("action=query&format=xml&list=allusers&augroup=bot&aulimit=500")

            If Result IsNot Nothing AndAlso Result.Contains("<allusers>") Then
                Result = Result.Substring(Result.IndexOf("<allusers>"))
                Result = Result.Substring(Result.IndexOf(">") + 1)
                Result = Result.Substring(0, Result.IndexOf("</allusers>"))
                Result = HtmlDecode(Result)

                For Each Item As String In Result.Split(New String() {"<u"}, StringSplitOptions.RemoveEmptyEntries)
                    If Item.Contains("""") Then
                        Dim Username As String = Item.Substring(Item.IndexOf("""") + 1)
                        Username = Username.Substring(0, Username.IndexOf(""""))
                        Bots.Add(Username)
                    End If
                Next Item
            End If

            If State = States.Cancelled Then Thread.CurrentThread.Abort()

            'Get watchlist
            UpdateStatus(Msg("login-progress-watchlist"))

            Result = GetText("title=Special:Watchlist/raw")

            If Result Is Nothing Then
                Abort(Msg("login-error-watchlist"))
                Exit Sub
            End If

            If Result.Contains("<textarea ") Then
                Result = Result.Substring(Result.IndexOf("<textarea "))
                Result = Result.Substring(Result.IndexOf(">") + 1)
                Result = Result.Substring(0, Result.IndexOf("</textarea>"))
                Result = HtmlDecode(Result)

                For Each Item As String In Result.Split(New String() {LF}, StringSplitOptions.RemoveEmptyEntries)
                    Dim ThisPage As Page = GetPage(Item)
                    If Not Watchlist.Contains(ThisPage) Then Watchlist.Add(ThisPage)
                Next Item

                Callback(AddressOf LoginForm.Done)
                Complete()
            Else
                Fail()
            End If
        End Sub

        Private Sub ShowNewVersionForm()
            Dim NewForm As New UpdateForm
            NewForm.ShowDialog()
        End Sub

        Private Sub CaptchaNeeded()
            Dim NewCaptchaForm As New CaptchaForm
            NewCaptchaForm.CaptchaId = CaptchaId

            If NewCaptchaForm.ShowDialog = DialogResult.OK Then
                CaptchaWord = NewCaptchaForm.Answer.Text
                Start()
            Else
                CaptchaId = Nothing
                CaptchaWord = Nothing
                Abort(Msg("login-error-nocaptcha"))
            End If
        End Sub

        Private Sub UpdateStatus(ByVal Message As String)
            If Not LoginForm.LoggingIn Then Thread.CurrentThread.Abort()
            Callback(AddressOf LoginForm.UpdateStatus, CObj(Message))
        End Sub

        Private Sub Abort(ByVal Message As String)
            Callback(AddressOf LoginForm.Abort, CObj(Message))
            Fail()
        End Sub

    End Class

End Namespace